package com.api.league.service.impl;

import com.api.base.utils.PageInfoUtils;
import com.api.core.service.AbstractService;
import com.api.league.dao.LeagueFixtureMapper;
import com.api.league.dao.LeagueMapper;
import com.api.league.dao.LeagueMatchDayMapper;
import com.api.league.dao.LeagueStandingMapper;
import com.api.league.enums.FixtureStatus;
import com.api.league.form.BatchAddFixturesForm;
import com.api.league.form.FixtureForm;
import com.api.league.model.LeagueFixture;
import com.api.league.model.LeagueMatchDay;
import com.api.league.model.LeagueStanding;
import com.api.league.service.LeagueMatchService;
import com.api.league.vo.LeagueFixtureVo;
import com.api.league.vo.LeagueMatchDayVo;
import com.api.league.vo.LeagueStandingVo;
import com.api.user.service.TeamService;
import com.api.user.vo.TeamVo;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Condition;
import tk.mybatis.mapper.util.Sqls;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author RenQiang
 * @date 2021/5/30
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class LeagueMatchServiceImpl extends AbstractService<LeagueMatchDay> implements LeagueMatchService {
    private final LeagueMatchDayMapper leagueMatchDayMapper;
    private final LeagueFixtureMapper leagueFixtureMapper;
    private final LeagueMapper leagueMapper;
    private final LeagueStandingMapper leagueStandingMapper;
    private final TeamService teamService;

    public LeagueMatchServiceImpl(LeagueFixtureMapper leagueFixtureMapper, LeagueMapper leagueMapper, LeagueMatchDayMapper leagueMatchDayMapper, LeagueStandingMapper leagueStandingMapper, TeamService teamService) {
        this.leagueFixtureMapper = leagueFixtureMapper;
        this.leagueMapper = leagueMapper;
        this.leagueMatchDayMapper = leagueMatchDayMapper;
        this.leagueStandingMapper = leagueStandingMapper;
        this.teamService = teamService;
    }

    @Override
    public void batchAddFixtures(BatchAddFixturesForm form) {
        // 查询已存在的比赛日
        List<Integer> matchDayNos = form.getMatchDays().stream()
                .map(BatchAddFixturesForm.MatchDayForm::getNo)
                .collect(Collectors.toList());
        List<LeagueMatchDay> existMatchDays = apiMapper.selectByCondition(Condition.builder(LeagueMatchDay.class)
                .where(Sqls.custom().andEqualTo("leagueId", form.getLeagueId())
                        .andIn("no", matchDayNos))
                .build());
        Map<Integer, LeagueMatchDay> existMatchDayNoMap = existMatchDays.stream()
                .collect(Collectors.toMap(LeagueMatchDay::getNo, Function.identity()));
        // 新增不存在的比赛日
        List<LeagueMatchDay> insetMatchDays = form.getMatchDays().stream()
                .filter(matchDayForm -> !existMatchDayNoMap.containsKey(matchDayForm.getNo()))
                .map(matchDayForm -> {
                    LeagueMatchDay leagueMatchDay = new LeagueMatchDay();
                    leagueMatchDay.setLeagueId(form.getLeagueId());
                    leagueMatchDay.setNo(matchDayForm.getNo());
                    return leagueMatchDay;
                }).collect(Collectors.toList());
        if (!insetMatchDays.isEmpty()) {
            super.save(insetMatchDays);
            insetMatchDays.forEach(matchDay -> existMatchDayNoMap.put(matchDay.getNo(), matchDay));
            leagueMapper.countMatchDay(form.getLeagueId());
        }
        // 新增对局
        List<LeagueFixture> insertFixtures = form.getMatchDays().stream()
                .flatMap(matchDayForm -> matchDayForm.getFixtures().stream()
                        .map(fixtureForm -> {
                            LeagueFixture leagueFixture = new LeagueFixture();
                            leagueFixture.setMatchDayId(existMatchDayNoMap.get(matchDayForm.getNo()).getId());
                            leagueFixture.setPlayTime(fixtureForm.getPlayTime());
                            leagueFixture.setStatus(FixtureStatus.NotStart);
                            leagueFixture.setTeam1Id(fixtureForm.getTeam1Id());
                            leagueFixture.setTeam2Id(fixtureForm.getTeam2Id());
                            return leagueFixture;
                        }))
                .collect(Collectors.toList());
        leagueFixtureMapper.insertList(insertFixtures);
    }

    @Override
    public void deleteFixtureById(Long fixtureId) {
        LeagueFixture leagueFixture = leagueFixtureMapper.selectByPrimaryKey(fixtureId);
        if (leagueFixture.getStatus() == FixtureStatus.Finished) {
            throw new IllegalArgumentException("已完成的对局不可删除");
        }
        leagueFixtureMapper.deleteByPrimaryKey(fixtureId);
    }

    @Override
    public void deleteMatchDayById(Long matchDayId) {
        List<LeagueFixture> matchDayFixtures = leagueFixtureMapper.selectByCondition(Condition.builder(LeagueFixture.class)
                .where(Sqls.custom().andEqualTo("matchDayId", matchDayId))
                .build());
        if (matchDayFixtures.stream()
                .anyMatch(leagueFixture -> leagueFixture.getStatus() == FixtureStatus.Finished)) {
            throw new IllegalArgumentException("比赛日有已完成的对局，不可删除");
        }
        leagueMatchDayMapper.deleteByPrimaryKey(matchDayId);
        if (!matchDayFixtures.isEmpty()) {
            leagueFixtureMapper.deleteByIds(matchDayFixtures.stream()
                    .map(LeagueFixture::getId)
                    .map(Object::toString)
                    .collect(Collectors.joining(",")));
        }
    }

    @Override
    public PageInfo<LeagueMatchDayVo> findMatchDayByPage(Long leagueId, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<LeagueMatchDay> leagueMatchDays = leagueMatchDayMapper.findMatchDayByPage(leagueId);
        if (leagueMatchDays.isEmpty()) {
            return new PageInfo<>();
        }
        // 保留分页信息
        PageInfo<LeagueMatchDay> pageInfo = new PageInfo<>(leagueMatchDays);
        List<LeagueFixture> leagueFixtures = leagueFixtureMapper.selectByCondition(Condition.builder(LeagueFixture.class)
                .where(Sqls.custom().andIn("matchDayId", leagueMatchDays.stream()
                        .map(LeagueMatchDay::getId)
                        .collect(Collectors.toList())))
                .build());
        List<Long> teamIds = leagueFixtures.stream()
                .flatMap(fixture -> Stream.of(fixture.getTeam1Id(), fixture.getTeam2Id()))
                .distinct()
                .collect(Collectors.toList());
        Map<Long, TeamVo> teamIdVoMap = teamService.getIdVoMap(teamIds);

        Map<Long, List<LeagueFixture>> matchDayGroupedList = leagueFixtures.stream()
                .collect(Collectors.groupingBy(LeagueFixture::getMatchDayId));
        // 比赛日没有对局则返回emptyList
        leagueMatchDays.forEach(matchDay -> matchDayGroupedList.putIfAbsent(matchDay.getId(), Collections.emptyList()));
        List<LeagueMatchDayVo> matchDayVos = leagueMatchDays.stream().map(leagueMatchDay -> {
            LeagueMatchDayVo matchDayVo = new LeagueMatchDayVo();
            matchDayVo.setId(leagueMatchDay.getId());
            matchDayVo.setNo(leagueMatchDay.getNo());
            matchDayVo.setFixtures(matchDayGroupedList.get(leagueMatchDay.getId()).stream()
                    .map(leagueFixture -> {
                        LeagueFixtureVo fixtureVo = new LeagueFixtureVo();
                        fixtureVo.setId(leagueFixture.getId());
                        fixtureVo.setPlayTime(leagueFixture.getPlayTime());
                        fixtureVo.setTeam1Id(leagueFixture.getTeam1Id());
                        fixtureVo.setTeam2Id(leagueFixture.getTeam2Id());
                        fixtureVo.setTeam1(teamIdVoMap.get(leagueFixture.getTeam1Id()));
                        fixtureVo.setTeam2(teamIdVoMap.get(leagueFixture.getTeam2Id()));
                        fixtureVo.setTeam1Score(leagueFixture.getTeam1Score());
                        fixtureVo.setTeam2Score(leagueFixture.getTeam2Score());
                        return fixtureVo;
                    })
                    .sorted(Comparator.comparing(LeagueFixtureVo::getPlayTime))
                    .collect(Collectors.toList()));
            return matchDayVo;
        }).collect(Collectors.toList());
        return PageInfoUtils.convertPageInfo(pageInfo, matchDayVos);
    }

    @Override
    public PageInfo<LeagueFixtureVo> findFixtureByPage(Long leagueId, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<LeagueFixtureVo> fixtureVos = leagueFixtureMapper.findFixtureByPage(leagueId);
        PageInfo<LeagueFixtureVo> pageInfo = new PageInfo<>(fixtureVos);
        List<Long> teamIds = fixtureVos.stream()
                .flatMap(fixture -> Stream.of(fixture.getTeam1Id(), fixture.getTeam2Id()))
                .distinct()
                .collect(Collectors.toList());
        Map<Long, TeamVo> teamIdVoMap = teamService.getIdVoMap(teamIds);
        for (LeagueFixtureVo fixtureVo : fixtureVos) {
            fixtureVo.setTeam1(teamIdVoMap.get(fixtureVo.getTeam1Id()));
            fixtureVo.setTeam2(teamIdVoMap.get(fixtureVo.getTeam2Id()));
        }
        return pageInfo;
    }

    @Override
    public void saveFixtureResultAndSettle(Long fixtureId, Integer team1Score, Integer team2Score) {
        LeagueFixture leagueFixture = leagueFixtureMapper.selectByPrimaryKey(fixtureId);
        if (leagueFixture == null) {
            throw new IllegalArgumentException("对局不存在");
        }
        if (leagueFixture.getPlayTime().after(new Date())) {
            throw new IllegalArgumentException("比赛未开始，不能保存结果");
        }
        int saved = leagueFixtureMapper.saveResult(fixtureId, team1Score, team2Score);
        if (saved == 1) {
            // 计算standing
            Pair<LeagueStanding, LeagueStanding> standingPair = calcStanding(leagueFixture, team1Score, team2Score);
            leagueStandingMapper.saveOrUpdateStandings(Lists.newArrayList(standingPair.getLeft(), standingPair.getRight()));
        }
    }

    @Override
    public PageInfo<LeagueStandingVo> findMatchDayStandingByPage(Long leagueId, Date beginDate, Date endDate, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<LeagueStanding> standings = leagueStandingMapper.findMatchDayStandingByPage(leagueId, beginDate, endDate);
        PageInfo<LeagueStanding> pageInfo = new PageInfo<>(standings);
        return convertStandingPageInfo(pageInfo);
    }

    @Override
    public PageInfo<LeagueStandingVo> findTeamStandingByPage(Long leagueId, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<LeagueStanding> standings = leagueStandingMapper.findTeamStandingByPage(leagueId);
        PageInfo<LeagueStanding> pageInfo = new PageInfo<>(standings);
        return convertStandingPageInfo(pageInfo);
    }

    @Override
    public void batchUpdateFixtures(List<FixtureForm> fixtureForms) {
        Map<Long, FixtureForm> fixtureIdMap = fixtureForms.stream()
                .collect(Collectors.toMap(FixtureForm::getFixtureId, Function.identity()));
        String idStr = fixtureIdMap.keySet().stream()
                .map(Object::toString)
                .collect(Collectors.joining(","));
        List<LeagueFixture> leagueFixtures = leagueFixtureMapper.selectByIds(idStr);
        Date now = new Date();
        if (leagueFixtures.stream().anyMatch(lf -> lf.getPlayTime().before(now))) {
            throw new IllegalArgumentException("无法修改以开始的对局");
        }
        if (leagueFixtures.stream().anyMatch(lf -> lf.getStatus() == FixtureStatus.Finished)) {
            throw new IllegalArgumentException("无法修改已完成的对局");
        }
        for (LeagueFixture leagueFixture : leagueFixtures) {
            FixtureForm fixtureForm = fixtureIdMap.get(leagueFixture.getId());
            if (fixtureForm != null) {
                leagueFixture.setPlayTime(fixtureForm.getPlayTime());
                leagueFixture.setTeam1Id(fixtureForm.getTeam1Id());
                leagueFixture.setTeam2Id(fixtureForm.getTeam2Id());
                leagueFixtureMapper.updateByPrimaryKey(leagueFixture);
            }
        }
    }

    private PageInfo<LeagueStandingVo> convertStandingPageInfo(PageInfo<LeagueStanding> pageInfo) {
        if (CollectionUtils.isEmpty(pageInfo.getList())) {
            return PageInfoUtils.convertPageInfo(pageInfo, Collections.emptyList());
        }
        String matchDayIdStr = pageInfo.getList().stream()
                .map(LeagueStanding::getMatchDayId)
                // 查询战队战绩（按战队分组）时不需要比赛日编号
                .filter(Objects::nonNull)
                .distinct()
                .map(Object::toString)
                .collect(Collectors.joining(","));
        Map<Long, LeagueMatchDay> matchDayIdMap;
        if (StringUtils.isNotEmpty(matchDayIdStr)) {
            List<LeagueMatchDay> leagueMatchDays = leagueMatchDayMapper.selectByIds(matchDayIdStr);
            matchDayIdMap = leagueMatchDays.stream()
                    .collect(Collectors.toMap(LeagueMatchDay::getId, Function.identity()));
        } else {
            matchDayIdMap = Collections.emptyMap();
        }

        List<Long> teamIds = pageInfo.getList().stream()
                .map(LeagueStanding::getTeamId)
                .distinct()
                .collect(Collectors.toList());
        Map<Long, TeamVo> teamIdVoMap = teamService.getIdVoMap(teamIds);
        List<LeagueStandingVo> leagueStandingVos = pageInfo.getList().stream()
                .map(leagueStanding -> {
                    LeagueStandingVo leagueStandingVo = new LeagueStandingVo();
                    BeanUtils.copyProperties(leagueStanding, leagueStandingVo);
                    leagueStandingVo.setTeam(teamIdVoMap.get(leagueStanding.getTeamId()));
                    leagueStandingVo.setMatchDayNo(Optional.ofNullable(matchDayIdMap.get(leagueStanding.getMatchDayId())).map(LeagueMatchDay::getNo).orElse(null));
                    return leagueStandingVo;
                })
                .collect(Collectors.toList());
        return PageInfoUtils.convertPageInfo(pageInfo, leagueStandingVos);
    }

    /**
     * 根据录入的成绩计算Standing
     * 返回本场Standing，需要累加到最终Standing中去
     * W - 赢	赢的话，points = 2分
     * D - 平手	平的话，points = 1分
     * L - 输	输的话，points = 0分
     *
     * @param leagueFixture 对局信息
     * @param team1Score    战队1成绩
     * @param team2Score    战队2成绩
     * @return Pair 战队1Standing -> 战队2Standing
     */
    static Pair<LeagueStanding, LeagueStanding> calcStanding(LeagueFixture leagueFixture, int team1Score, int team2Score) {
        // 战队1胜
        if (team1Score > team2Score) {
            LeagueStanding standing1 = new LeagueStanding(leagueFixture.getMatchDayId(), leagueFixture.getTeam1Id(), 1, 1, 0, 0, team1Score, team2Score, team1Score - team2Score, 2);
            LeagueStanding standing2 = new LeagueStanding(leagueFixture.getMatchDayId(), leagueFixture.getTeam2Id(), 1, 0, 0, 1, team2Score, team1Score, team2Score - team1Score, 0);
            return Pair.of(standing1, standing2);
        }
        // 战队2胜
        if (team2Score > team1Score) {
            LeagueStanding standing1 = new LeagueStanding(leagueFixture.getMatchDayId(), leagueFixture.getTeam1Id(), 1, 0, 0, 1, team1Score, team2Score, team1Score - team2Score, 0);
            LeagueStanding standing2 = new LeagueStanding(leagueFixture.getMatchDayId(), leagueFixture.getTeam2Id(), 1, 1, 0, 0, team2Score, team1Score, team2Score - team1Score, 2);
            return Pair.of(standing1, standing2);
        }
        // 平局
        LeagueStanding standing1 = new LeagueStanding(leagueFixture.getMatchDayId(), leagueFixture.getTeam1Id(), 1, 0, 1, 0, team1Score, team2Score, 0, 1);
        LeagueStanding standing2 = new LeagueStanding(leagueFixture.getMatchDayId(), leagueFixture.getTeam2Id(), 1, 0, 1, 0, team2Score, team1Score, 0, 1);
        return Pair.of(standing1, standing2);
    }
}
